home *** CD-ROM | disk | FTP | other *** search
- /*
- [IP:]
- ***** BEGIN LICENSE BLOCK *****
-
- Copyright (c) 2006 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://chnm.gmu.edu
-
- Licensed under the Educational Community License, Version 1.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.opensource.org/licenses/ecl1.php
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
- ***** END LICENSE BLOCK *****
-
- StumbleUpon revisions include:
- 1. Changes namespace.
- 2. Changes prototype syntax.
- 3. Changes bracket convention.
- 4. Adds missing semicolons.
- 5. Replaces Zotero.getZoteroDatabase() with new method
- this.getDBFile().
- 6. Replaces Zotero.getZoteroDirectory() with new method
- this._getStorageDirectory().
- 7. Replaces Zotero.moveToUnique() with new method
- this._moveToUniqueFile().
- 8. New debug logging that doesn't rely on the Zotero object.
- 9. New user interaction that doesn't rely on the Zotero object.
- 10. Adds this._query and the a, as, av, als, alv, q, avr and v
- methods.
- 11. Makes query() always return an array for SELECT queries.
- 12. Added _parent and automatic backup disabling.
- 13. Added _modified and made automatic backup dependent on
- _modified.
- 14. Disabled prompt for restart upon detecting corruption.
- 15. Added _hostIsWindows().
- */
-
- var su_DatabaseConnection = function(parent, dbName)
- {
- this.skipBackup = false;
-
- // Private members
- this._parent = parent;
- this._dbName = dbName;
- this._modified = false;
- this._shutdown = false;
- this._connection = null;
- this._transactionRollback = null;
- this._transactionNestingLevel = 0;
- this._callbacks = { begin: [], commit: [], rollback: [] };
- this._dbIsCorrupt = null;
- this._self = this;
- this._query = "";
- }
- su_DatabaseConnection.prototype =
- { // BEGIN prototype
-
- /////////////////////////////////////////////////////////////////
- //
- // Public methods
- //
- /////////////////////////////////////////////////////////////////
-
- /*
- * Run an SQL query
- *
- * Optional _params_ is an array of bind parameters in the form
- * [1,"hello",3] or [{'int':2},{'string':'foobar'}]
- *
- * Returns:
- * - Associative array (similar to mysql_fetch_assoc) for SELECT's
- * - lastInsertId for INSERT's
- * - TRUE for other successful queries
- * - FALSE on error
- */
- query: function (sql,params)
- {
- var db = this._getDBConnection();
- if (! sql)
- sql = this._query;
-
- try {
- // Parse out the SQL command being used
- var op = sql.match(/^[^a-z]*[^ ]+/i);
- if (op)
- op = op.toString().toLowerCase();
-
- // If SELECT statement, return result
- if (op=='select')
- {
- // Until the native dataset methods work (or at least exist),
- // we build a multi-dimensional associative array manually
-
- var statement = this.getStatement(sql, params);
-
- var dataset = new Array();
- while (statement.executeStep())
- {
- var row = new Array();
-
- for(var i=0, len=statement.columnCount; i<len; i++)
- row[statement.getColumnName(i)] = this._getTypedValue(statement, i);
- dataset.push(row);
- }
- statement.reset();
-
- return dataset;
- }
- else
- {
- this._modified = true;
- if (params)
- {
- var statement = this.getStatement(sql, params);
- statement.execute();
- }
- else
- {
- this._debug(sql,5);
- db.executeSimpleSQL(sql);
- }
-
- if (op=='insert')
- return db.lastInsertRowID;
- // DEBUG: Can't get affected rows for UPDATE or DELETE?
- else
- return true;
- }
- }
- catch (e) {
- this.checkException(e);
-
- var dberr = (db.lastErrorString!='not an error')
- ? ' [ERROR: ' + db.lastErrorString + ']' : '';
- throw(e + ' [QUERY: ' + sql + ']' + dberr);
- }
- },
-
-
- /*
- * Query a single value and return it
- */
- valueQuery: function (sql,params)
- {
- var statement = this.getStatement(sql, params);
-
- // No rows
- if (!statement.executeStep())
- {
- statement.reset();
- return false;
- }
-
- var value = this._getTypedValue(statement, 0);
- statement.reset();
- return value;
- },
-
-
- /*
- * Run a query and return the first row
- */
- rowQuery: function (sql,params)
- {
- var result = this.query(sql,params);
- if (result.length)
- return result[0];
- else
- return new Array();
- },
-
-
- /*
- * Run a query and return the first column as a numerically-indexed array
- */
- columnQuery: function (sql,params)
- {
- var statement = this.getStatement(sql, params);
-
- if (statement)
- {
- var column = new Array();
- while (statement.executeStep())
- column.push(this._getTypedValue(statement, 0));
-
- statement.reset();
- return column.length ? column : false;
- }
- return false;
- },
-
- a: function (str)
- {
- this._query = str;
- },
-
- av: function (str)
- {
- str = ((parseInt(str)) + "");
- if (str == "NaN")
- str = "0";
- this._query += str + ",";
- },
-
- avr: function (str)
- {
- str = ((parseInt(str)) + "");
- if (str == "NaN")
- str = "0";
- this._query += str;
- },
-
- as: function (str)
- {
- this._query += "'" + str.replace(/'/g, "''") + "',";
- },
-
- alv: function (str)
- {
- str = ((parseInt(str)) + "");
- if (str == "NaN")
- str = "0";
- this._query += str + ")";
- },
-
- als: function (str)
- {
- this._query += "'" + str.replace(/'/g, "''") + "')";
- },
-
- escapeString: function (str)
- {
- this._query += str.replace(/'/g, "''");
- },
-
- q: function (str)
- {
- return "'" + str.replace(/'/g, "''") + "'";
- },
-
- v: function (str)
- {
- str = ((parseInt(str)) + "");
- if (str == "NaN")
- str = "0";
- return str;
- },
-
- /*
- /*
- * Get a raw mozStorage statement from the DB for manual processing
- *
- * This should only be used externally for manual parameter binding for
- * large repeated queries
- *
- * Optional _params_ is an array of bind parameters in the form
- * [1,"hello",3] or [{'int':2},{'string':'foobar'}]
- */
- getStatement: function (sql, params)
- {
- var db = this._getDBConnection();
-
- try {
- this._debug(sql,5);
- var statement = db.createStatement(sql);
- }
- catch (e) {
- var dberr = (db.lastErrorString!='not an error')
- ? ' [ERROR: ' + db.lastErrorString + ']' : '';
- throw(e + ' [QUERY: ' + sql + ']' + dberr);
- }
-
- if (params)
- {
- // If single scalar value or single non-array object, wrap in an array
- if (typeof params != 'object' || params===null ||
- (params && typeof params == 'object' && !params.length))
- params = [params];
-
- for (var i=0; i<params.length; i++)
- {
- // Integer
- if (params[i]!==null && typeof params[i]['int'] != 'undefined')
- {
- var type = 'int';
- var value = params[i]['int'];
- }
- // String
- else if (params[i]!==null && typeof params[i]['string'] != 'undefined')
- {
- var type = 'string';
- var value = params[i]['string'];
- }
- // Null
- else if (params[i]!==null && typeof params[i]['null'] != 'undefined')
- {
- var type = 'null';
- }
- // Automatic (trust the JS type)
- else
- {
- switch (typeof params[i])
- {
- case 'string':
- var type = 'string';
- break;
- case 'number':
- var type = 'int';
- break;
- // Object
- default:
- if (params[i]===null)
- {
- var type = 'null';
- }
- else
- {
- throw('Invalid bound parameter ' + params[i]);
- // throw('Invalid bound parameter ' + params[i] +
- // ' in ' + Zotero.varDump(params));
- }
- }
- var value = params[i];
- }
-
- // Bind the parameter as the correct type
- switch (type)
- {
- case 'int':
- this._debug('Binding parameter ' + (i+1)
- + ' of type int: ' + value, 5);
- statement.bindInt32Parameter(i, value);
- break;
-
- case 'string':
- this._debug('Binding parameter ' + (i+1)
- + ' of type string: "' + value + '"', 5);
- statement.bindUTF8StringParameter(i, value);
- break;
-
- case 'null':
- this._debug('Binding parameter ' + (i+1)
- + ' of type NULL', 5);
- statement.bindNullParameter(i);
- break;
- }
- }
- }
- return statement;
- },
-
-
- /*
- * Only for use externally with this.getStatement()
- */
- getLastInsertID: function ()
- {
- var db = this._getDBConnection();
- return db.lastInsertRowID;
- },
-
-
- /*
- * Only for use externally with this.getStatement()
- */
- getLastErrorString: function ()
- {
- var db = this._getDBConnection();
- return db.lastErrorString;
- },
-
-
- beginTransaction: function ()
- {
- var db = this._getDBConnection();
-
- if (db.transactionInProgress)
- {
- this._transactionNestingLevel++;
- this._debug('Transaction in progress -- increasing level to '
- + this._transactionNestingLevel, 5);
- }
- else
- {
- this._debug('Beginning DB transaction', 5);
- db.beginTransaction();
-
- // Run callbacks
- for (var i=0; i<this._callbacks.begin.length; i++)
- {
- if (this._callbacks.begin[i])
- this._callbacks.begin[i]();
- }
- }
- },
-
-
- commitTransaction: function ()
- {
- var db = this._getDBConnection();
-
- if (this._transactionNestingLevel)
- {
- this._transactionNestingLevel--;
- this._debug('Decreasing transaction level to ' + this._transactionNestingLevel, 5);
- }
- else if (this._transactionRollback)
- {
- this._debug('Rolling back previously flagged transaction', 5);
- this.rollbackTransaction();
- }
- else
- {
- this._debug('Committing transaction',5);
- try {
- db.commitTransaction();
-
- // Run callbacks
- for (var i=0; i<this._callbacks.commit.length; i++)
- {
- if (this._callbacks.commit[i])
- this._callbacks.commit[i]();
- }
- }
- catch(e) {
- var dberr = (db.lastErrorString!='not an error')
- ? ' [ERROR: ' + db.lastErrorString + ']' : '';
- throw(e + dberr);
- }
- }
- },
-
-
- rollbackTransaction: function ()
- {
- var db = this._getDBConnection();
-
- if (!db.transactionInProgress)
- {
- this._debug("Transaction is not in progress in rollbackTransaction()", 2);
- return;
- }
-
- if (this._transactionNestingLevel)
- {
- this._transactionNestingLevel--;
- this._transactionRollback = true;
- this._debug('Flagging nested transaction for rollback', 5);
- }
- else
- {
- this._debug('Rolling back transaction', 5);
- this._transactionRollback = false;
- try {
- db.rollbackTransaction();
-
- // Run callbacks
- for (var i=0; i<this._callbacks.rollback.length; i++)
- {
- if (this._callbacks.rollback[i])
- this._callbacks.rollback[i]();
- }
- }
- catch(e) {
- var dberr = (db.lastErrorString!='not an error')
- ? ' [ERROR: ' + db.lastErrorString + ']' : '';
- throw(e + dberr);
- }
- }
- },
-
-
- addCallback: function (type, cb)
- {
- switch (type)
- {
- case 'begin':
- case 'commit':
- case 'rollback':
- break;
-
- default:
- throw ("Invalid callback type '" + type + "' in DB.addCallback()");
- }
-
- var id = this._callbacks[type].length;
- this._callbacks[type][id] = cb;
- return id;
- },
-
-
- removeCallback: function (type, id)
- {
- switch (type)
- {
- case 'begin':
- case 'commit':
- case 'rollback':
- break;
-
- default:
- throw ("Invalid callback type '" + type + "' in DB.removeCallback()");
- }
-
- delete this._callbacks[type][id];
- },
-
-
- transactionInProgress: function ()
- {
- var db = this._getDBConnection();
- return db.transactionInProgress;
- },
-
-
- /**
- * Safety function used on shutdown to make sure we're not stuck in the
- * middle of a transaction
- *
- * NOTE: No longer used
- */
- commitAllTransactions: function ()
- {
- if (this.transactionInProgress())
- {
- var level = this._transactionNestingLevel;
- this._transactionNestingLevel = 0;
- try {
- this.commitTransaction();
- }
- catch (e) {}
- return level ? level : true;
- }
- return false;
- },
-
-
- /*
- * Used on shutdown to rollback all open transactions
- */
- rollbackAllTransactions: function ()
- {
- if (this.transactionInProgress())
- {
- var level = this._transactionNestingLevel;
- this._transactionNestingLevel = 0;
- try {
- this.rollbackTransaction();
- }
- catch (e) {}
- return level ? level : true;
- }
- return false;
- },
-
-
- tableExists: function (table)
- {
- return this._getDBConnection().tableExists(table);
- },
-
-
- getColumns: function (table)
- {
- var db = this._getDBConnection();
-
- try {
- var sql = "SELECT * FROM " + table + " LIMIT 1";
- var statement = this.getStatement(sql);
- var cols = new Array();
- for (var i=0,len=statement.columnCount; i<len; i++)
- cols.push(statement.getColumnName(i));
- statement.reset();
- return cols;
- }
- catch (e) {
- this._debug(e,1);
- return false;
- }
- },
-
-
- getColumnHash: function (table)
- {
- var cols = this.getColumns(table);
- var hash = {};
- if (cols.length)
- {
- for (var i=0; i<cols.length; i++)
- hash[cols[i]] = true;
- }
- return hash;
- },
-
-
- /**
- * Find the lowest unused integer >0 in a table column
- *
- * Note: This retrieves all the rows of the column, so it's not really
- * meant for particularly large tables.
- **/
- getNextID: function (table, column)
- {
- var sql = 'SELECT ' + column + ' FROM ' + table + ' ORDER BY ' + column;
- var vals = this.columnQuery(sql);
-
- if (!vals)
- return 1;
-
- if (vals[0] === '0')
- vals.shift();
-
- for (var i=0, len=vals.length; i<len; i++)
- {
- if (vals[i] != i+1)
- break;
- }
-
- return i+1;
- },
-
-
- /**
- * Find the next lowest numeric suffix for a value in table column
- *
- * For example, if "Untitled" and "Untitled 2" and "Untitled 4",
- * returns "Untitled 3"
- *
- * DEBUG: doesn't work once there's an "Untitled 10"
- *
- * If _name_ alone is available, returns that
- **/
- getNextName: function (table, field, name)
- {
- var sql = "SELECT " + field + " FROM " + table + " WHERE " + field
- + " LIKE ? ORDER BY " + field + " COLLATE NOCASE";
- var untitleds = this.columnQuery(sql, name + '%');
-
- if (!untitleds || untitleds[0]!=name)
- return name;
-
- var i = 1;
- var num = 2;
- while (untitleds[i] && untitleds[i]==(name + ' ' + num))
- {
- while (untitleds[i+1] && untitleds[i]==untitleds[i+1])
- {
- this._debug('Next ' + i + ' is ' + untitleds[i]);
- i++;
- }
-
- i++;
- num++;
- }
-
- return name + ' ' + num;
- },
-
-
- /*
- * Shutdown observer -- implements nsIObserver
- */
- observe: function(subject, topic, data)
- {
- switch (topic)
- {
- case 'xpcom-shutdown':
- this.destroy();
-
- break;
- }
- },
-
- destroy: function ()
- {
- if (this._shutdown)
- {
- this._debug('returning');
- return;
- }
-
- // NOTE: disabled
- //var level = this.commitAllTransactions();
- var level = this.rollbackAllTransactions();
- if (level)
- {
- level = level === true ? '0' : level;
- this._debug("A transaction in DB '" + this._dbName + "' was still open! (level " + level + ")", 2);
- }
-
- this._shutdown = true;
-
- this.skipBackup = (this.skipBackup || (! this._modified) || (! this._parent.getValue("@enable_db_backup")));
-
- this.backupDatabase();
-
- this._parent = null;
- },
-
- integrityCheck: function ()
- {
- var ok = this.valueQuery("PRAGMA integrity_check");
- return ok == 'ok';
- },
-
-
- checkException: function (e)
- {
- if (e.name && e.name == 'NS_ERROR_FILE_CORRUPTED')
- {
- // Write corrupt marker to data directory
- var file = this.getDBFile(this._dbName, 'is.corrupt');
- var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
- .createInstance(Components.interfaces.nsIFileOutputStream);
- foStream.init(file, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
- foStream.write('', 0);
- foStream.close();
-
- this._dbIsCorrupt = true;
-
- // var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
- // .getService(Components.interfaces.nsIPromptService);
- //
- // var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING)
- // + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_IS_STRING);
- //
- // var index = ps.confirmEx(null,
- // Zotero.getString('general.error'),
- // Zotero.getString('db.dbCorrupted', this._dbName) + '\n\n' + Zotero.getString('db.dbCorrupted.restart'),
- // buttonFlags,
- // Zotero.getString('general.restartNow'),
- // Zotero.getString('general.restartLater'),
- // null, null, {});
- //
- // if (index == 0)
- // {
- // var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
- // .getService(Components.interfaces.nsIAppStartup);
- // appStartup.quit(Components.interfaces.nsIAppStartup.eRestart);
- // appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit);
- // }
- //
- // Zotero.skipLoading = true;
- // return false;
- }
- return true;
- },
-
-
- backupDatabase: function (suffix)
- {
- if (this.transactionInProgress())
- {
- this._debug("Transaction in progress--skipping backup of DB '" + this._dbName + "'", 2);
- return false;
- }
-
- var corruptMarker = this.getDBFile(this._dbName, 'is.corrupt').exists();
-
- if (this.skipBackup)
- {
- this._debug("Skipping backup of database '" + this._dbName + "'", 1);
- return false;
- }
- else if (this._dbIsCorrupt || corruptMarker)
- {
- this._debug("Database '" + this._dbName + "' is marked as corrupt--skipping backup", 1);
- return false;
- }
-
- this._debug("Backing up database '" + this._dbName + "'");
-
- var file = this.getDBFile(this._dbName);
- var backupFile = this.getDBFile(this._dbName,
- (suffix ? suffix + '.' : '') + 'bak');
-
- // Copy via a temporary file so we don't run into disk space issues
- // after deleting the old backup file
- var tmpFile = this.getDBFile(this._dbName, 'tmp');
- if (tmpFile.exists())
- tmpFile.remove(null);
-
- try {
- file.copyTo(file.parent, tmpFile.leafName);
- }
- catch (e){
- // TODO: deal with low disk space
- throw (e);
- }
-
- // Opened database files can't be moved on Windows, so we have to skip
- // the extra integrity check (unless we wanted to write two copies of
- // the database, but that doesn't seem like a great idea)
- if (!this._hostIsWindows())
- {
- try {
- var store = Components.classes["@mozilla.org/storage/service;1"].
- getService(Components.interfaces.mozIStorageService);
-
- var connection = store.openDatabase(tmpFile);
- }
- catch (e){
- this._debug("Database file '" + tmpFile.leafName + "' is corrupt--skipping backup");
- if (tmpFile.exists())
- tmpFile.remove(null);
- return false;
- }
- }
-
- // Remove old backup file
- if (backupFile.exists())
- backupFile.remove(null);
-
- tmpFile.moveTo(tmpFile.parent, backupFile.leafName);
-
- return true;
- },
-
- _hostIsWindows: function ()
- {
- var outval = true;
- try {
- var appinfo = Components.classes["@mozilla.org/xre/app-info;1"]
- .getService(Components.interfaces.nsIXULAppInfo);
- appinfo = appinfo.QueryInterface(Components.interfaces.nsIXULRuntime);
-
- // spec.id = appinfo.ID;
- // spec.version = appinfo.version;
- outval = (appinfo.OS.toLowerCase().indexOf("win") != -1);
- } catch (e) {}
-
- return outval;
- },
-
- /*
- * Keep the SQLite shared cache live between transactions with a dummy statement,
- * which speeds up DB access dramatically (at least on Windows and Linux--OS X
- * seems to be much faster already, perhaps due to its own disk cache)
- *
- * This is the same technique used by Mozilla code. The one downside is that it
- * prevents schema changes, so this is called after schema updating. If the
- * schema really needs to be updated at another point, use stopDummyStatement().
- *
- * See http://developer.mozilla.org/en/docs/Storage:Performance for more info.
- */
- startDummyStatement: function ()
- {
- // try {
- if (!this._dummyConnection)
- {
- this._debug("Opening database '" + this._dbName + " for dummy statement");
- // Get the storage service
- var store = Components.classes["@mozilla.org/storage/service;1"].
- getService(Components.interfaces.mozIStorageService);
- var file = this.getDBFile(this._dbName);
- this._dummyConnection = store.openDatabase(file);
- }
-
- if (this._dummyStatement)
- {
- this._debug("Dummy statement is already open");
- return;
- }
-
- this._debug("Initializing dummy statement for '" + this._dbName + "'");
-
- var sql = "CREATE TABLE IF NOT EXISTS dummyTable (id INTEGER PRIMARY KEY)";
- this._dummyConnection.executeSimpleSQL(sql);
-
- sql = "INSERT OR IGNORE INTO dummyTable VALUES (1)";
- this._dummyConnection.executeSimpleSQL(sql);
-
- sql = "SELECT id FROM dummyTable LIMIT 1";
- this._dummyStatement = this._dummyConnection.createStatement(sql);
- this._dummyStatement.executeStep();
-
- // }
- // catch (e) {
- // Components.utils.reportError(e);
- // this._debug(e);
- // }
- },
-
-
- /*
- * Stop the dummy statement temporarily to allow for schema changess
- *
- * The statement needs to be started again or performance will suffer.
- */
- stopDummyStatement: function ()
- {
- if (!this._dummyStatement)
- return;
-
- this._debug("Stopping dummy statement for '" + this._dbName + "'");
- this._dummyStatement.reset();
- this._dummyStatement = null;
- },
-
-
- getDBFile: function (name, ext)
- {
- var str;
-
- if (name)
- str = name;
- else if (this._dbName)
- str = this._dbName;
- else
- str = 'stumbleupon';
-
- str += '.sqlite';
-
- str += (ext) ? ('.' + ext) : '';
-
- var file = this._getStorageDirectory();
-
- file.append(str);
-
- return file;
- },
-
-
- /////////////////////////////////////////////////////////////////
- //
- // Private methods
- //
- /////////////////////////////////////////////////////////////////
-
- _getStorageDirectory: function ()
- {
- var file = Components.classes["@mozilla.org/file/directory_service;1"]
- .getService(Components.interfaces.nsIProperties)
- .get("ProfD", Components.interfaces.nsIFile);
-
- file.append("StumbleUpon");
- if (! file.exists())
- file.create(file.DIRECTORY_TYPE, 0700);
-
- return file;
- },
-
- _moveToUniqueFile: function (file, newFile)
- {
- newFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0644);
- var newName = newFile.leafName;
- newFile.remove(null);
-
- // Move file to unique name
- file.moveTo(newFile.parent, newName);
- return file;
- },
-
- /*
- * Retrieve a link to the data store
- */
- _getDBConnection: function ()
- {
- if (this._connection)
- return this._connection;
-
- this._debug("Opening database '" + this._dbName + "'");
-
- // Get the storage service
- var store = Components.classes["@mozilla.org/storage/service;1"].
- getService(Components.interfaces.mozIStorageService);
-
- var file = this.getDBFile(this._dbName);
- var backupFile = this.getDBFile(this._dbName, 'bak');
-
- var fileName = this._dbName + '.sqlite';
-
- // if (this._dbName == 'zotero' && ZOTERO_CONFIG['DB_REBUILD'])
- // {
- // if (confirm('Erase all user data and recreate database from schema?'))
- // {
- // // Delete existing Zotero database
- // if (file.exists())
- // file.remove(null);
- //
- // // Delete existing storage folder
- // var dir = Zotero.getStorageDirectory();
- // if (dir.exists())
- // dir.remove(true);
- // }
- // }
-
- catchBlock: try {
- var corruptMarker = this.getDBFile(this._dbName, 'is.corrupt');
- if (corruptMarker.exists())
- throw({ name: 'NS_ERROR_FILE_CORRUPTED' })
- this._connection = store.openDatabase(file);
- }
- catch (e) {
- if (e.name=='NS_ERROR_FILE_CORRUPTED')
- {
- this._debug("Database file '" + file.leafName + "' corrupted", 1);
-
- // No backup file! Eek!
- if (!backupFile.exists())
- {
- this._debug("No backup file for DB '" + this._dbName + "' exists", 1);
-
- // Save damaged filed
- this._debug('Saving damaged DB file with .damaged extension', 1);
- var damagedFile = this.getDBFile(this._dbName, 'damaged');
- this._moveToUniqueFile(file, damagedFile);
-
- // Create new main database
- var file = this.getDBFile(this._dbName);
- this._connection = store.openDatabase(file);
-
- if (corruptMarker.exists())
- corruptMarker.remove(null);
-
- // alert(Zotero.getString('db.dbCorruptedNoBackup', fileName));
- break catchBlock;
- }
-
- // Save damaged file
- this._debug('Saving damaged DB file with .damaged extension', 1);
- var damagedFile = this.getDBFile(this._dbName, 'damaged');
- this._moveToUniqueFile(file, damagedFile);
-
- // Test the backup file
- try {
- this._connection = store.openDatabase(backupFile);
- }
- // Can't open backup either
- catch (e) {
- // Create new main database
- var file = this.getDBFile(this._dbName);
- this._connection = store.openDatabase(file);
-
- // alert(Zotero.getString('db.dbRestoreFailed', fileName));
-
- if (corruptMarker.exists())
- corruptMarker.remove(null);
-
- break catchBlock;
- }
-
- this._connection = undefined;
-
- // Copy backup file to main DB file
- this._debug("Restoring database '" + this._dbName + "' from backup file", 1);
- try {
- backupFile.copyTo(backupFile.parent, fileName);
- }
- catch (e) {
- // TODO: deal with low disk space
- throw (e);
- }
-
- // Open restored database
- var file = this._getStorageDirectory();
- file.append(fileName);
- this._connection = store.openDatabase(file);
- this._debug('Database restored', 1);
- // var msg = Zotero.getString('db.dbRestored', [
- // fileName,
- // Zotero.Date.getFileDateString(backupFile),
- // Zotero.Date.getFileTimeString(backupFile)
- // ]);
- // alert(msg);
-
- if (corruptMarker.exists())
- corruptMarker.remove(null);
-
- break catchBlock;
- }
-
- // Some other error that we don't yet know how to deal with
- throw (e);
- }
-
- // Register shutdown handler to call this.onShutdown() for DB backup
- var observerService = Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- observerService.addObserver(this, "xpcom-shutdown", false);
- observerService = null;
-
- return this._connection;
- },
-
-
- _debug: function (str, level)
- {
- //this._parent._logError(false, {}, "DB ", str);
- },
-
-
- _getTypedValue: function (statement, i)
- {
- var type = statement.getTypeOfIndex(i);
- switch (type)
- {
- case statement.VALUE_TYPE_INTEGER:
- return statement.getInt64(i);
- case statement.VALUE_TYPE_TEXT:
- return statement.getUTF8String(i);
- case statement.VALUE_TYPE_NULL:
- return null;
- case statement.VALUE_TYPE_FLOAT:
- return statement.getDouble(i);
- case statement.VALUE_TYPE_BLOB:
- return statement.getBlob(i);
- default:
- return null;
- }
- }
-
- } // END prototype
-